﻿using Core = Cofe.Core;
using Cofe.Core.Property;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Cofe.Core.Utils;
using Cofe.Core.Validater;

namespace Cofe.Core.Property
{
    public abstract class TransformingPropertyProviderBase : PropertyProviderBase<IPropertyHost>
    {
        #region Constructor

        protected TransformingPropertyProviderBase(IPropertyHost propertyHost,
            IPropertyDefinitions[] supportedDefinitions)
            : base(propertyHost)
        {

        }


        #endregion

        #region Methods

        //protected bool findPropertyProvider(object property, out IPropertyProvider propertyProvider)
        //{
        //    foreach (var pp in Entity)
        //        if (pp.SupportedDefinitions.IsPropertySupported(property))
        //        {
        //            propertyProvider = pp;
        //            return true;
        //        }
        //    propertyProvider = null;
        //    return false;
        //}

        //protected bool findPropertyProvider(object property)
        //{
        //    IPropertyProvider provider;
        //    return findPropertyProvider(property, out provider);
        //}

        protected void registerTransformProperties<I, O>(object inputProperty, object outputProperty, Func<I, O> transformFunc)
        {
            if (Entity.Behaviors.IsPropertySupported(inputProperty)) //Only register if input property is supported.
                registerProperties(outputProperty, () =>
                    {
                        return transformFunc(Entity.Behaviors.GetProperty(inputProperty).ValueAs<I>());
                    });
        }


        protected void registerTransformAction<I, O>(object inputProperty, object outputProperty, Func<ParameterDic, I, O> transformFunc = null)
        {
            bool inputAsync = AttributeUtils<ActionAttribute>.FindAttribute(inputProperty).ActionType == ActionType.Async;
            bool outputAsync = AttributeUtils<ActionAttribute>.FindAttribute(outputProperty).ActionType == ActionType.Async;
            if (inputAsync != outputAsync)
                throw new ArgumentException("Either input is sync or output is async or vice versa.");
            if (inputAsync)
                registerActionAync<I, O>(inputProperty, outputProperty, transformFunc);
            else registerAction<I, O>(inputProperty, outputProperty, transformFunc);
        }


        private void registerAction<I, O>(object inputProperty, object outputProperty, Func<ParameterDic, I, O> transformFunc = null)
        {
            if (Entity.Behaviors.IsPropertySupported(inputProperty))
                registerProperties(outputProperty,
                    () =>
                    {
                        if (transformFunc == null)
                        {
                            if (typeof(I).Equals(typeof(O)))
                                return Entity.Behaviors.GetProperty(inputProperty).Value;
                            else throw new ArgumentNullException("transformFunc cannot be null if input and output type are different.");
                        }
                        var inputAction = Entity.Behaviors.GetProperty(inputProperty).ValueAs<Func<ParameterDic, I>>();
                        Func<ParameterDic, O> outputAction = (pm) => transformFunc(pm, inputAction(pm));
                        return outputAction;
                    });
        }

        private void registerActionAync<I, O>(object inputProperty, object outputProperty, Func<ParameterDic, I, O> transformFunc = null)
        {
            if (Entity.Behaviors.IsPropertySupported(inputProperty))
                registerProperties(outputProperty,
                    () =>
                    {
                       
                            if (transformFunc == null)
                            {
                                if (typeof(I).Equals(typeof(O)))
                                    return Entity.Behaviors.GetProperty(inputProperty).Value;
                                else throw new ArgumentNullException("transformFunc cannot be null if input and output type are different.");
                            }
                            Func<ParameterDic, Task<I>> inputAction =
                                Entity.Behaviors.GetPropertyAsync(inputProperty).Result.Value as Func<ParameterDic, Task<I>>;
                            Func<ParameterDic, Task<O>> outputAction =
                                (pm) => inputAction(pm).ContinueWith<O>((tk) => transformFunc(pm, tk.Result));
                            return outputAction;

                    });
        }

        #endregion

        #region Data

        #endregion

        #region Public Properties

        #endregion

    }
}
